package edu.uw.nemo.model;

import edu.uw.nemo.io.IntegerArraySerializer;

import java.util.*;

public class Mapping {

    private final List<String[]> adjList;
    private final Map<String, Integer> nodeToId;
    private final Map<Integer, String> idToNode;
    private final Map<Integer, Set<Integer>> adjMapping;

    public Mapping(List<String[]> adjList, Map<String, Integer> nodeToId, Map<Integer, String> idToNode, Map<Integer, Set<Integer>> adjMapping) {
        this.adjList = adjList;
        this.nodeToId = nodeToId;
        this.idToNode = idToNode;
        this.adjMapping = adjMapping;
    }

    public int getNodeCount() {
        return (adjMapping != null) ? adjMapping.size() : 0;
    }

    public int getLinkCount() {
        return adjList != null ? adjList.size() : 0;
    }

    public List<Integer> getIds() {
        return new ArrayList<Integer>(adjMapping.keySet());
    }

    public List<Integer> getNeighbours(Integer node) {
        return new ArrayList<Integer>(this.adjMapping.get(node));
    }

    public List<int[]> getEdges(int[] subGraph) {
        ArrayList<int[]> links = new ArrayList<int[]>();
        for (int from = 0; from < subGraph.length; from++) {
            Set<Integer> neighbours = new HashSet<Integer>(getNeighbours(subGraph[from]));
            for (int to = from + 1; to < subGraph.length; to++) {
                if (neighbours.contains(subGraph[to])) {
                    links.add(makeLink(from, to));
                }
            }
        }

        return links;
    }

    private int[] makeLink(int from, int to) {
        int[] link = new int[2];
        link[0] = from;
        link[1] = to;
        return link;
    }

    public Map<Integer,Set<Integer>> getAdj() {
        return this.adjMapping;
    }

    int numberOfPartitions(int k, int nodes) {
        int basis = k * k;
        if ( basis < nodes ) {
            basis = nodes;
        }
        int multiplier = ( k < 3 ) ? 3 : k;

        return primeAbove(basis * multiplier);
    }

    private int primeAbove(int lowerBound) {
        int upperBound = (lowerBound * 2) + 2;
        if (lowerBound % 2 == 0) lowerBound++;
        int result = upperBound + 1;
        for (int index = lowerBound; index <= upperBound; index = index + 2) {
            if (isPrime(index)) {
                result = index;
                break;
            }
        }

        return result;
    }

    private boolean isPrime(int index) {
        int end = ( (int) (Math.sqrt(index)) ) + 1;
        for (int div = 3; div < end; div = div + 2) {
            if (index % div == 0) return false;
        }
        return true;
    }

    int[] countLinks(int depth) {
        int count = getNodeCount();
        int[] result = new int[count];
        // direct links
        for (int i = 0; i < count; i++) {
            result[i] = adjMapping.get(i).size();
        }
        for (int d = 1; d < depth; d++) {
            result = updateLinkCount(result);
        }

        return result;
    }

    private int[] updateLinkCount(int[] input) {
        int count = input.length;
        int[] result = new int[count];
        for (int i = 0; i < count; i++) {
            result[i] = input[i];
            Set<Integer> links = adjMapping.get(i);
            Iterator<Integer> iterator = links.iterator();
            while (iterator.hasNext()) {
                int idx = iterator.next();
                result[i] += input[idx];
            }
        }
        return result;
    }

    public List<Integer> getVertices(int depth, int partitions) {
        List<Integer> byCounts = getVerticesSortedByLinkCount(depth, getIds());
        return getVerticesGroupedByPartitionCount(partitions, byCounts);
    }

    List<Integer> getVerticesSortedByLinkCount(int depth, List<Integer> vertices) {
        int[] links = countLinks(depth);

        // sort by link count
        List<Integer> byCounts = new ArrayList<Integer>(vertices);
        Collections.sort(byCounts, new LinkCountComparator(links));
        return byCounts;
    }

    List<Integer> getVerticesGroupedByPartitionCount(int partitions, List<Integer> vertices) {
        // reverseMap map positions (for use in hash comparator
        List<Integer> partitionedVertices = getIds();
        Collections.sort(partitionedVertices, new HashComparator(partitions));

        List<Integer> groupedVertices = new ArrayList<Integer>();
        for (int index = 0; index < vertices.size(); index++) {
            groupedVertices.add(vertices.get(partitionedVertices.get(index)));
        }

        return groupedVertices;
    }

}

class LinkCountComparator implements Comparator<Integer> {

    private final int[] counts;

    public LinkCountComparator(int[] links) {
        this.counts = links;
    }

    public int compare(Integer first, Integer second) {
        int comp = counts[second] - counts[first];
        if ( comp == 0 ) {
            comp = first - second;
        }

        return comp;
    }

}

class HashComparator implements Comparator<Integer> {

    private final int partitions;

    public HashComparator(int numPart) {
        this.partitions = numPart;
    }

    public int compare(Integer first, Integer second) {
        int comp = (first % partitions) - (second % partitions);
        return comp;
    }

}

